package com.dustedpixels.jasmin.chips.z80.util;

import java.util.Random;

import com.dustedpixels.jasmin.chips.z80.Z80;
import com.dustedpixels.jasmin.chips.z80.Z80Context;
import com.dustedpixels.jasmin.chips.z80.Z80Factory;
import com.dustedpixels.jasmin.chips.z80.impl.Z80Version1Factory;
import com.dustedpixels.jasmin.chips.z80.impl.Z80Version2Factory;
import com.dustedpixels.jasmin.chips.z80.impl.Z80Version3Factory;
import com.dustedpixels.jasmin.chips.z80.impl.Z80Version4Factory;
import com.dustedpixels.jasmin.chips.z80.impl.Z80VersionQAOPFactory;

/**
 * Measures performance of Z80 implementation.
 */
public class Z80Benchmark {
    /**
     * The number of tests per single instruction.
     */
    private static final int TESTS = 10000;
    
    /**
     * Random number generator
     */
    private final Random random = new Random();

    /**
     * Generates random Z80 state.
     */
    private Z80RandomStateGenerator randomStateGenerator =
        new Z80RandomStateGenerator();

    /**
     * Measures performance of Z80 chip obtained through specified factory, and
     * prints result on standard output.
     * 
     * @param factory The factory.
     */
    public void run(Z80Factory factory) {
        final int[] mem = new int[5];

        Z80Context context = new StubZ80Context() {
            public byte peek(short address) {
                int index = address & 0xFFFF;
                
                if (index < mem.length) {
                    return (byte) mem[index];
                } else {
                    return 0x27;
                }
            }
        };

        Z80 z80 = factory.createZ80(context);

        long seed = random.nextLong();

        randomStateGenerator.generateRandomState(z80, seed);
        
        long start = System.currentTimeMillis();
        
        // Test basic instruction set.
        for (int i = 0; i <= 0xFF; i++) {
            // Skip extended instructions.
            if (i == 0xCB || i == 0xDD || i == 0xED || i == 0xFD)
                continue;

            // Initialize instruction
            mem[0] = i;
            mem[1] = random.nextInt() & 0xFF;
            mem[2] = random.nextInt() & 0xFF;
            mem[3] = random.nextInt() & 0xFF;
            mem[4] = random.nextInt() & 0xFF;

            for (int j = 0; j < TESTS; j++) {
                z80.setPC((short) 0);

                // Process one instruction
                z80.processInstruction();
            }
        }

        // Test CB instruction set.
        for (int i = 0; i <= 0xFF; i++) {
            // Initialize instruction
            mem[0] = 0xCB;
            mem[1] = i;
            mem[2] = random.nextInt() & 0xFF;
            mem[3] = random.nextInt() & 0xFF;
            mem[4] = random.nextInt() & 0xFF;

            for (int j = 0; j < TESTS; j++) {
                z80.setPC((short) 0);

                z80.processInstruction();
            }
        }

        // Test ED instruction set.
        for (int i = 0; i <= 0xFF; i++) {
            // Initialize instruction
            mem[0] = 0xED;
            mem[1] = i;
            mem[2] = random.nextInt() & 0xFF;
            mem[3] = random.nextInt() & 0xFF;
            mem[4] = random.nextInt() & 0xFF;

            for (int j = 0; j < TESTS; j++) {
                z80.setPC((short) 0);

                z80.processInstruction();
            }
        }

        // Test DD instruction set.
        for (int i = 0; i <= 0xFF; i++) {
            // Skip extended instructions.
            if (i == 0xCB || i == 0xDD || i == 0xED || i == 0xFD)
                continue;
            
            // Initialize instruction
            mem[0] = 0xDD;
            mem[1] = i;
            mem[2] = random.nextInt() & 0xFF;
            mem[3] = random.nextInt() & 0xFF;
            mem[4] = random.nextInt() & 0xFF;

            for (int j = 0; j < TESTS; j++) {
                z80.setPC((short) 0);
                
                z80.processInstruction();
            }
        }

        // Test FD instruction set.
        for (int i = 0; i <= 0xFF; i++) {
            // Skip extended instructions.
            if (i == 0xCB || i == 0xDD || i == 0xED || i == 0xFD)
                continue;
            
            // Initialize instruction
            mem[0] = 0xFD;
            mem[1] = i;
            mem[2] = random.nextInt() & 0xFF;
            mem[3] = random.nextInt() & 0xFF;
            mem[4] = random.nextInt() & 0xFF;

            for (int j = 0; j < TESTS; j++) {
                z80.setPC((short) 0);
                
                z80.processInstruction();
            }
        }

        // Test DDCB instruction set.
        // TODO: Only documented ones.
        for (int i = 0x06; i <= 0xFF; i+=0x08) {
            // Initialize instruction
            mem[0] = 0xDD;
            mem[1] = 0xCB;
            mem[2] = random.nextInt() & 0xFF;
            mem[3] = i;
            mem[4] = random.nextInt() & 0xFF;

            for (int j = 0; j < TESTS; j++) {
                z80.setPC((short) 0);
                
                z80.processInstruction();
            }
        }

        // Test FDCB instruction set.
        // TODO: Only documented ones.
        for (int i = 0x06; i <= 0xFF; i+=0x08) {
            // Initialize instruction
            mem[0] = 0xFD;
            mem[1] = 0xCB;
            mem[2] = random.nextInt() & 0xFF;
            mem[3] = i;
            mem[4] = random.nextInt() & 0xFF;

            for (int j = 0; j < TESTS; j++) {
                z80.setPC((short) 0);
                
                z80.processInstruction();
            }
        }
        
        System.out.println("Time [ms]: " + (System.currentTimeMillis() - start));
    }

    public static void main(String[] args) {
        Z80Benchmark benchmark = new Z80Benchmark();

        System.out.println("Testing Z80Version1");
        benchmark.run(new Z80Version1Factory());
        
        System.out.println("Testing Z80Version2");
        benchmark.run(new Z80Version2Factory());

        System.out.println("Testing Z80Version3");
        benchmark.run(new Z80Version3Factory());
        
        System.out.println("Testing Z80Version4");
        benchmark.run(new Z80Version4Factory());

        System.out.println("Testing Z80VersionQAOP");
        benchmark.run(new Z80VersionQAOPFactory());
    }
}
